package com.wwl.base.common.excel.utils;

import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * @author wwl
 * @version 1.0
 * @date 2021/2/8 14:44
 * @description:
 */
public abstract class ListUtils {
    public static <T> List<T> newList(Iterable<T> iterable) {
        List<T> list = new ArrayList<>();
        for (T it : iterable) {
            list.add(it);
        }
        return list;
    }

    /**
     * 类型转换
     *
     * @param list   数据源列表
     * @param mapper 转换器
     * @param <T>    原始类型
     * @param <R>    目标类型
     * @return 转换后的列表
     */
    public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
        Assert.notNull(list);
        Assert.notNull(mapper);
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        return list.stream().map(mapper).collect(Collectors.toList());
    }

    /**
     * 将列表中itemT的一或多个K类型的字段，合并输出到List<K>列表中
     * <p>
     * <code>
     * 场景参考
     * <p>
     * 某订单列表中有createUserId,lastUpdateUserId,closeUserId三个字段
     * 先需要将列表中这三个字段的userId合并为一个userIdList
     * <p>
     * List<Integer>userIdList = selectListProperties(
     * orders,
     * order->order.getCreateUserId(), //简写：Order::getCreateUserId
     * order->order.getLastUpdateUserId(),
     * order->order.getCloseUserId()
     * );
     * </code>
     *
     * @param list      数据源
     * @param selectors 字段选择器列表
     * @param <T>       数据源类型
     * @param <K>       选中的字段类型
     * @return 选中的字段对应的值混合列表
     */
    @SafeVarargs
    public static <T, K> List<K> selectListProperties(List<T> list, Function<T, K>... selectors) {
        Assert.notNull(list);
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }

        List<K> values = new ArrayList<>();
        list.forEach(it -> {
            for (Function<T, K> selector : selectors) {
                values.add(selector.apply(it));
            }
        });

        return values.stream().filter(Objects::nonNull)
                .distinct().collect(Collectors.toList());
    }

    /**
     * 批量设置字段：参考列表项的某个字段设置另外一个字段
     * <p>
     * <code>
     * 场景参考：
     * <p>
     * 某订单列表中有createUserId,createUser字段，
     * 现需要根据createUserId字段，批量通过provider读取其对应用户实体列表，并设置order的createUser
     * <p>
     * setListProperty(
     * orders,
     * order->order.getCreateUserId(),
     * userIds->userRepository.findById(userIds),//provider直接从数据源批量查询用户信息
     * (order,user)->order.setCreateUser(user)
     * )
     * </code>
     *
     * @param list     数据源
     * @param selector 参考字段选择器
     * @param provider 数据提供者
     * @param setter   目标字段设置器
     * @param <T>      列表中数据项类型
     * @param <K>      参考字段类型
     * @param <V>      目标字段类型
     */
    @SuppressWarnings("unchecked")
    public static <T, K, V> void setListProperty(
            List<T> list,
            Function<T, K> selector,
            Function<List<K>, Map<K, V>> provider,
            BiConsumer<T, V> setter
    ) {
        Assert.notNull(list);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        List<K> keyList = selectListProperties(list, selector);
        Map<K, V> map = provider.apply(keyList);

        list.forEach(it -> {
            K key = selector.apply(it);
            V value = map.get(key);
            setter.accept(it, value);
        });
    }

    /**
     * 批量设置列表中的两个字段：参考列表项的某2个字段设置另外2个字段
     * 参考setListProperty方法，在其基础上支持一次处理两个字段
     *
     * @param list      数据源
     * @param provider  数据提供者
     * @param selector1 参考字段选择器1
     * @param setter1   目标字段设置器1
     * @param selector2 参考字段选择器2
     * @param setter2   目标字段设置器2
     * @param <T>       列表中数据项类型
     * @param <K>       参考字段类型
     * @param <V>       目标字段类型
     */
    public static <T, K, V> void setList2Properties(
            List<T> list,
            Function<List<K>, Map<K, V>> provider,
            Function<T, K> selector1,
            BiConsumer<T, V> setter1,
            Function<T, K> selector2,
            BiConsumer<T, V> setter2
    ) {
        Assert.notNull(list);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        List<K> keyList = selectListProperties(list, selector1, selector2);
        Map<K, V> map = provider.apply(keyList);

        list.forEach(it -> {
            K key1 = selector1.apply(it);
            K key2 = selector2.apply(it);
            V value1 = map.get(key1);
            V value2 = map.get(key2);
            setter1.accept(it, value1);
            setter2.accept(it, value2);
        });
    }

    /**
     * 批量设置列表中的多个字段：参考列表项的某个字段，从提供者中提取数据，并将对应结果应用到多个目标字段设置器
     * 使用场景：按照列表中的主键字段，读取对应的计数器，并设置其对应的多个计数字段
     *
     * @param list     数据源
     * @param selector 参考字段选择器
     * @param provider 数据提供者
     * @param setters  目标字段设置器数组
     * @param <T>      列表中数据项类型
     * @param <K>      参考字段类型
     * @param <V>      关联数据类型
     */
    @SafeVarargs
    public static <T, K, V> void setListProperties(
            List<T> list,
            Function<T, K> selector,
            Function<List<K>, Map<K, V>> provider,
            BiConsumer<T, V>... setters
    ) {
        Assert.notNull(list);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        List<K> keyList = selectListProperties(list, selector);
        Map<K, V> map = provider.apply(keyList);

        list.forEach(it -> {
            K key = selector.apply(it);
            V value = map.get(key);
            for (BiConsumer<T, V> setter : setters) {
                setter.accept(it, value);
            }
        });
    }
}
